<?php


namespace app\common\services;


use think\Exception;

class RedisCluster
{
    //是否使用 M/s 的读写集群方案
    private $_isUseCluster = false;

    // Slave 句柄标记
    private $_sn = 0;

    //服务器连接句柄
    private $_linkHandle = [
        'master'=>null,// 只支持一台 Master
        'slave'=>[],// 可以有多台 Slave
    ];

    /**
     * 构造函数
     * RedisCluster constructor.
     * @param false $isUseCluster
     */
    public function __construct($isUseCluster = false){
        $this->_isUseCluster = $isUseCluster;
    }

    /**
     * 连接服务器，这儿使用长连接 提高效率
     * @param array $config Redis 服务器配置
     * @param bool $isMaster 当前服务器是否为 Master服务器
     */
    public function connect($config = [],$isMaster = true){
        if(!isset($config['port'])){
            $config['port'] = 6379;
        }
        if($isMaster){
            $this->_linkHandle['master'] = new \Redis();
            $ret = $this->_linkHandle['master']->connect($config['host'],$config['port']);

            if($config['pwd']){
                $ret = $this->_linkHandle['master']->auth($config['pwd']);
                if($ret !== true) throw new Exception('redis密码不正确');
            }
        }else{
            //多个 Slave 连接
            $this->_linkHandle['slave'][$this->_sn] = new \Redis();
            $ret = $this->_linkHandle['slave'][$this->_sn]->connect($config['host'],$config['port']);
            if($config['pwd']){
                $ret = $this->_linkHandle['master']->auth($config['pwd']);
                if($ret !== true)  throw new Exception('redis密码不正确');
            }
            ++$this->_sn;
        }
        return $ret;
    }

    /**
     * 关闭连接
     * @param int $flag  0 关闭 Master  1 关闭 Slave 2关闭所有
     */
    public function close($flag = 2){
        switch ($flag){
            case 0:
                $this->getRedis()->close();break;
            case 1:
                for($i=0; $i<$this->_sn; ++$i){
                    $this->_linkHandle['slave'][$i]->close();
                }
                break;
            case 2:
                $this->getRedis()->close();
                for($i=0; $i<$this->_sn; ++$i){
                    $this->_linkHandle['slave'][$i]->close();
                }
                break;
        }
        return true;
    }

    /**
     * 得到原始操作对象
     * @param bool $isMaster 返回服务器的类型 true:返回Master false:返回Slave
     * @param bool $slaveOne 返回的Slave选择 true:负载均衡随机返回一个Slave选择 false:返回所有的Slave选择
     */
    public function getRedis($isMaster = true,$slaveOne = true){
        if($isMaster){
            return $this->_linkHandle['master'];
        }else{
            return $slaveOne ? $this->_getSlaveRedis():$this->_linkHandle['slave'];
        }
    }

    /**
     * 写缓存
     * @param $key  缓存key
     * @param $value  缓存值
     * @param int $expire  过期时间秒,0代表不过期
     */
    public function set($key,$value,$expire=0){
        if($expire==0){
            $ret = $this->getRedis()->set($key,$value);
        }else{
            $ret = $this->getRedis()->setex($key,$expire,$value);
        }
        return $ret;
    }

    /**
     * 读缓存
     * @param $key
     */
    public function get($key){
        $func = is_array($key) ? 'mGet':'get';
        //没有使用M/S
        if(!$this->_isUseCluster){
            return $this->getRedis()->{$func}($key);
        }
        // 使用了M/s
        return $this->_getSlaveRedis()->{$func}($key);
    }

    /**
     * 条件型设置缓存，如果key 不存在就设置 存在就设置失败
     * @param $key
     * @param $value
     */
    public function setnx($key,$value){
        return $this->getRedis()->setnx($key,$value);
    }

    /**
     * 删除缓存
     * @param $key
     */
    public function remove($key){
        return $this->getRedis()->delete($key);
    }

    /**
     * 增加数值
     * @param $key
     * @param int $default
     */
    public function incr($key,$default=1){
        if($default==1){
            return $this->getRedis()->incr($key);
        }else{
            return $this->getRedis()->incrBy($key,$default);
        }
    }

    /**
     * 减少数值
     * @param $key
     * @param int $default
     */
    public function decr($key,$default=1){
        if($default==1){
            return $this->getRedis()->decr($key);
        }else{
            return $this->getRedis()->decrBy($key,$default);
        }
    }

    /**
     * 清空当前数据库
     */
    public function clear(){
        return $this->getRedis()->flushDB();
    }


    /**
     * 向队列头部添加一个值 入栈
     * @param $key
     * @param $value
     */
    public function lpush($key,$value){
        return $this->getRedis()->lpush($key,$value);
    }

    /**
     * 从队列末尾 取出一个值 出栈
     * @param $key
     */
    public function lpop($key){
        return $this->getRedis()->lpop($key);
    }

    /**
     * 返回列表中指定区间内的元素
     * @param $key
     * @param $start
     * @param $end
     */
    public function lrange($key,$start,$end){
        return $this->getRedis()->lrange($key,$start,$end);
    }

    /**
     *  在哈希表中存储某个key
     */
    public function hset($name,$key,$value){
        if(is_array($value)){
            return $this->getRedis()->hset($name,$key,serialize($value));
        }
        return $this->getRedis()->hset($name,$key,$value);
    }
    /**
     *  在哈希表中获取某个key
     */
    public function hget($name,$key = null,$serialize=true){
        if($key){
            $row = $this->getRedis()->hget($name,$key);
            if($row && $serialize){
                unserialize($row);
            }
            return $row;
        }
        return $this->getRedis()->hgetAll($name);
    }
    /**
     *  删除某个哈希表的值
     */
    public function hdel($name,$key = null){
        if($key){
            return $this->getRedis()->hdel($name,$key);
        }
        return $this->getRedis()->hdel($name);
    }
    /**
     * 开启事务
     * Transaction start
     */
    public function multi(){
        return $this->getRedis()->multi();
    }
    /**
     * 执行事务
     * Transaction send
     */
    public function exec(){
        return $this->getRedis()->exec();
    }



    /**
     * 负载均衡 随机HASH获取 某个 Slave 服务器
     */
    private function _getSlaveRedis(){
        //只有一台
        if($this->_sn <= 1){
            return $this->_linkHandle['slave'][0];
        }
        $hash = $this->_hashId(mt_rand(),$this->_sn);
        return $this->_linkHandle['slave'][$hash];
    }

    /**
     * 根据ID 得到hash 后 0-m-1之间 的值
     * @param $id
     * @param $m
     */
    private function _hashId($id,$m){
        $k = md5($id);
        $l = strlen($k);
        $b = bin2hex($k);
        $h=0;
        for ($i=0;$i<$l;$i++){
            $h+=substr($b,$i*2,2);
        }
        $hash = ($h*1)%$m;
        return $hash;
    }
}

























